﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace Faml.Operations
{
    public delegate bool SelectPredecate<T>(T t);
    public delegate TR FuncParams<T0, TR>(params T0[] t);

    public class BaseOperations
    {
        #region Common Methods

        private static OperationLibrary lib;

        static BaseOperations()
        {
            lib = new OperationLibrary(null);
            lib.AddMethods(typeof(BaseOperations).Assembly);
        }

        public static Scalar<T> CallScalarExpression<T>(MethodInfo method, params object[] args)
            where T: struct
        {
            Expression[] param = new Expression[args.Length];
            for (int i = 0; i < args.Length; i++)
            {
                object arg = args[i];
                if (arg is Expression)
                    param[i] = (Expression)arg;
                else if (arg is LazyReference<T>)
                {
                    param[i] = ((LazyReference<T>)arg).Expression;
                }
            }
            Expression expr = Expression.Call(method, param);
            return new Scalar<T>(Expression.Lambda<Func<T>>(expr));
        }

        #endregion

        #region GetLength

        public static Scalar<int> GetLength<T>(Array1d<T> array)
        {
            Operation op = lib.GetOperation("GetLength", typeof(T), new Type[] { typeof(T[]) });

            Expression expr;

            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    array.Expression
                );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    array.Expression);

            return new Scalar<int>(Expression.Lambda<Func<int>>(expr));
        }

        public static Scalar<int> GetLength<T>(Array2d<T> array, Scalar<int> dimension)
        {
            Operation op = lib.GetOperation("GetLength", typeof(T), new Type[] { typeof(T[,]), typeof(int) });

            Expression expr;

            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    array.Expression, dimension.Expression
                );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    array.Expression, dimension.Expression);

            return new Scalar<int>(Expression.Lambda<Func<int>>(expr));
        }

        #endregion

        #region Subarrays

        static public Array1d<T> Subarray<T>(Array1d<T> a, Array1d<int> indices)
        {
            Operation op = lib.GetOperation("subarray", typeof(T), new Type[] { typeof(T[]), typeof(int[]) });

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    a.Expression, indices.Expression
                );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    a.Expression, indices.Expression);

            return new Array1d<T>(Expression.Lambda<Func<T[]>>(expr));
        }

        static public Array1d<T> Subarray<T>(Array1d<T> a, Scalar<int> index)
            where T : struct
        {
            Operation op = lib.GetOperation("subarray", typeof(T), new Type[] { typeof(T[]), typeof(int) });

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    a.Expression, index.Expression
                );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    a.Expression, index.Expression);

            return new Array1d<T>(Expression.Lambda<Func<T[]>>(expr));
        }

        static public Array2d<T> Subarray<T>(Array2d<T> a, Array1d<int> indicesX, Array1d<int> indicesY)
        {
            Operation op = lib.GetOperation("subarray", typeof(T), new Type[] { typeof(T[,]), typeof(int[]), typeof(int[]) });

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    a.Expression, indicesX.Expression, indicesY.Expression
                );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    a.Expression, indicesX.Expression, indicesY.Expression
                    );
                    
            return new Array2d<T>(Expression.Lambda<Func<T[,]>>(expr));
        }

        static public Array1d<T> Subarray<T>(Array2d<T> a, Array1d<int> indicesX, Scalar<int> indexY)
        {
            Operation op = lib.GetOperation("subarray", typeof(T), new Type[] { typeof(T[,]), typeof(int[]), typeof(int) });

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    a.Expression, indicesX.Expression, indexY.Expression
                );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    a.Expression, indicesX.Expression, indexY.Expression
                    );

            return new Array1d<T>(Expression.Lambda<Func<T[]>>(expr));
        }

        static public Array1d<T> Subarray<T>(Array2d<T> a, Scalar<int> indexX, Array1d<int> indicesY)
        {
            Operation op = lib.GetOperation("subarray", typeof(T), new Type[] { typeof(T[,]), typeof(int), typeof(int[]) });

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    a.Expression, indexX.Expression, indicesY.Expression
                );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    a.Expression, indexX.Expression, indicesY.Expression
                    );

            return new Array1d<T>(Expression.Lambda<Func<T[]>>(expr));
        }

        static public Scalar<T> Subarray<T>(Array2d<T> a, Scalar<int> indexX, Scalar<int> indexY)
            where T: struct
        {
            Operation op = lib.GetOperation("subarray", typeof(T), new Type[] { typeof(T[,]), typeof(int), typeof(int) });

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    a.Expression, indexX.Expression, indexY.Expression
                );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    a.Expression, indexX.Expression, indexY.Expression
                    );

            return new Scalar<T>(Expression.Lambda<Func<T>>(expr));
        }

        #endregion

        #region Fold

        static public Scalar<T> Fold<T>(Expression<Func<T, T, T>> func, Array1d<T> array)
            where T : struct
        {
            Operation op = lib.GetOperation("fold", typeof(T), new Type[] { typeof(Expression<Func<T, T, T>>), typeof(T[]) });

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    func, array.Expression
                );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    func, array.Expression
                    );

            return new Scalar<T>(Expression.Lambda<Func<T>>(expr));
        }

        #endregion

        #region Apply

        static public Array1d<T> Apply<T>(Expression<FuncParams<T, T>> func, params Array1d<T>[] arrays)
        {
            Operation op = lib.GetOperation("apply", typeof(T), new Type[] { typeof(Expression<FuncParams<T, T>>), typeof(T[][]) });
            Expression[] args = new Expression[arrays.Length];
            for (int i = 0; i < arrays.Length; i++)
            {
                args[i] = arrays[i].Expression;
            }

            Expression @params = Expression.NewArrayInit(typeof(T[]), args);

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    func,
                @params
                );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    func,
                @params
                    );

            return new Array1d<T>(Expression.Lambda<Func<T[]>>(expr));
        }

        static public Array2d<T> Apply<T>(Expression<FuncParams<T, T>> func, params Array2d<T>[] arrays)
        {
            Operation op = lib.GetOperation("apply", typeof(T), new Type[] { typeof(Expression<FuncParams<T, T>>), typeof(T[][,]) });

            Expression[] args = new Expression[arrays.Length];
            for (int i = 0; i < arrays.Length; i++)
            {
                args[i] = arrays[i].Expression;
            }

            Expression @params = Expression.NewArrayInit(typeof(T[]), args);

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    func,
                    @params
                );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    func,
                    @params
                    );

            return new Array2d<T>(Expression.Lambda<Func<T[,]>>(expr));
        }

        #endregion

        #region Select

        static public Array1d<T> Select<T>(Array1d<T> array, Expression<SelectPredecate<T>> predicate)
        {
            Operation op = lib.GetOperation("select", typeof(T), new Type[] { typeof(T[]), typeof(Expression<SelectPredecate<T>>) });

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    array.Expression, predicate
                    );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    array.Expression, predicate
                    );

            return new Array1d<T>(Expression.Lambda<Func<T[]>>(expr));
        }

        #endregion

        #region Unfold

        static public Array1d<T> Unfold<T>(Scalar<int> n, Expression<Func<int, T>> func)
            where T : struct
        {
            Operation op = lib.GetOperation("unfold", typeof(T), new Type[] { typeof(int), typeof(Expression<Func<int, T>>) });

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    n.Expression, func
                    );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    n.Expression, func
                    );

            return new Array1d<T>(Expression.Lambda<Func<T[]>>(expr));
        }

        static public Array2d<T> Unfold<T>(Scalar<int> n, Scalar<int> m, Expression<Func<int, int, T>> func)
            where T : struct
        {
            Operation op = lib.GetOperation("unfold", typeof(T), new Type[] { typeof(int), typeof(int), typeof(Expression<Func<int, int, T>>) });

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    n.Expression, m.Expression, func
                    );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    n.Expression, m.Expression, func
                    );

            return new Array2d<T>(Expression.Lambda<Func<T[,]>>(expr));
        }

        static public Array2d<T> Unfold<T>(Scalar<int> n, Expression<Func<int, T[]>> func)
        {
            Operation op = lib.GetOperation("unfold", typeof(T), new Type[] { typeof(int), typeof(Expression<Func<int, T[]>>) });

            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    n.Expression, func
                    );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    n.Expression, func
                    );

            return new Array2d<T>(Expression.Lambda<Func<T[,]>>(expr));
        }

        static public Array2d<T> Unfold<T>(Expression<Func<int, T[]>> func, Scalar<int> n)
        {
            Operation op = lib.GetOperation("unfold", typeof(T), new Type[] { typeof(Expression<Func<int, T[]>>), typeof(int) });


            Expression expr;
            if (op.IsStatic)
                expr = Expression.Call(
                    op.Method,
                    func, n.Expression
                    );
            else
                expr = Expression.Call(
                    Expression.Constant(op.InstanceObject),
                    op.Method,
                    func, n.Expression
                    );
           

            return new Array2d<T>(Expression.Lambda<Func<T[,]>>(expr));
        }

        #endregion
        
    }
}
